/*
 * Copyright (c) 2017 Sprint
 *
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the terms found in the LICENSE file in the root of this source tree.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "sthread.h"
#include "serror.h"
#include "satomic.h"

#include <sched.h>
#include <time.h>
#include <pthread.h>
#include <signal.h>

#include <iostream>

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

SThread::SThread(bool selfDestruct) : mSuspended(0, 1000), mSuspendSem(0, 1) {
  mSelfDestruct        = selfDestruct;
  mState               = SThread::rsWaitingToRun;
  mSuspendCnt          = 1;
  mArg                 = NULL;
  mExitCode            = 0;
  mKeepGoing           = true;
  mUpdateStateManually = false;

  mInitialized = false;
}

SThread::~SThread() {
  if (isRunning()) {
    _shutdown();
    join();
  }
}

void SThread::_shutdown() {
  SMutexLock l(mMutex);

  if (isRunning()) {
    suspend();
    shutdown();
    resume();
  }
}

void SThread::init(void* arg, bool suspended) {
  {
    SMutexLock l(mMutex);

    if (isInitialized())
      SError::throwRuntimeExceptionWithErrno("Thread already initialized");

    mArg = arg;

    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    if (pthread_create(&mThread, &attr, _threadProc, (void*) this) != 0)
      SError::throwRuntimeExceptionWithErrno("Error initializing thread");

    mInitialized = true;

    if (suspended) mSuspendCnt++;

    resume();
  }

  while (mSuspendCnt == 0 && isWaitingToRun()) yield();
}

bool SThread::isInitialized() {
  return mInitialized;
}

void SThread::resume() {
  if (!isInitialized())
    SError::throwRuntimeException("The semaphore is not initialized");

  {
    if (mSuspendCnt > 0) {
      mSuspendCnt--;
      if (mSuspendCnt == 0) {
        mSuspendSem.increment();
      }
    }
  }

  yield();
}

void SThread::suspend() {
  int suspendCnt;

  {
    SMutexLock l(mMutex);
    mSuspendCnt++;
    suspendCnt = mSuspendCnt;
  }

  if (suspendCnt == 1) mSuspended.decrement();
}

void SThread::_suspend() {
  mSuspended.increment();
  mSuspendSem.decrement();
}

void SThread::join() {
  {
    SMutexLock l(mMutex);
    if (!isRunning() && !isSuspended()) return;
  }

  void* value;
  pthread_join(mThread, &value);
}

void SThread::sleep(int milliseconds) {
  timespec tmReq;

  tmReq.tv_sec  = (time_t)(milliseconds / 1000);
  tmReq.tv_nsec = (milliseconds % 1000) * 1000 * 1000;

  // we're not interested in remaining time nor in return value
  (void) nanosleep(&tmReq, (timespec*) NULL);
}

void SThread::yield() {
  sched_yield();
}

void* SThread::_threadProc(void* arg) {
  SThread* ths = (SThread*) arg;

  // do not continue until resumed
  ths->mSuspendSem.decrement();

  // set to running state
  {
    SMutexLock l(ths->mMutex);
    if (!ths->getUpdateStateManually()) ths->mState = SThread::rsRunning;
  }

  unsigned long ret = ths->threadProc(ths->mArg);

  // set to not running state
  {
    SMutexLock l(ths->mMutex);
    if (!ths->getUpdateStateManually()) ths->mState = SThread::rsDoneRunning;
  }

  if (ths->mSelfDestruct) {
    pthread_t t = ths->mThread;
    delete ths;
    pthread_detach(t);
  }

  return (void*) ret;
}

extern "C" void _UserSignal1Handler(int) {}

void SThread::cancelWait() {
  mKeepGoing = false;

  signal(SIGUSR1, _UserSignal1Handler);
  pthread_kill(mThread, SIGUSR1);
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

SEventThread::SEventThread(bool selfDestruct) : SThread(selfDestruct) {}

SEventThread::~SEventThread() {}

void SEventThread::init(void* arg, bool suspended) {
  SThread::init(arg, suspended);
  postMessage(ETM_INIT);
}

void SEventThread::postMessage(uint16_t msg) {
  postMessage(new SEventThreadMessage(msg));
}

void SEventThread::postMessage(SEventThreadMessage* msg) {
  m_events.push(msg);
}

void SEventThread::quit() {
  postMessage(ETM_QUIT);
}

void SEventThread::suspend() {
  postMessage(ETM_SUSPEND);
  SThread::suspend();
}

void SEventThread::initTimer(SEventThread::Timer& t) {
  t.init(this);
}

void SEventThread::onInit() {}

void SEventThread::onQuit() {}

void SEventThread::onSuspend() {
  _suspend();
}

void SEventThread::onTimer(SEventThread::Timer& ptimer) {}

unsigned long SEventThread::threadProc(void* arg) {
  dispatch();
  return 0;
}

void SEventThread::dispatch() {
  bool done = false;

  while (!done) {
    SEventThreadMessage* m = (SEventThreadMessage*) m_events.pop();

    if (m) {
      switch (m->getId()) {
        case ETM_INIT:
          onInit();
          break;
        case ETM_QUIT:
          done = true;
          onQuit();
          break;
        case ETM_SUSPEND:
          onSuspend();
          break;
        case ETM_TIMER:
          onTimer(*((STimerMessage*) m)->getTimer());
          break;
        default:
          dispatch(*m);
          break;
      }

      delete m;
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

long SEventThread::Timer::m_nextid = 0;

SEventThread::Timer::Timer() {
  // assign the id
  m_id       = atomic_inc_fetch(m_nextid);
  m_thread   = NULL;
  m_interval = 0;
  m_oneshot  = true;

  m_timer = NULL;
}

SEventThread::Timer::Timer(long milliseconds, bool oneshot) {
  // assign the id
  m_id       = atomic_inc_fetch(m_nextid);
  m_thread   = NULL;
  m_interval = milliseconds;
  m_oneshot  = oneshot;

  m_timer = NULL;
}

SEventThread::Timer::~Timer() {
  destroy();
}

void SEventThread::Timer::init(SEventThread* pThread) {
  destroy();

  m_thread = pThread;

  struct sigevent sev;
  sev.sigev_notify          = SIGEV_SIGNAL;
  sev.sigev_signo           = SIGRTMIN;
  sev.sigev_value.sival_ptr = this;
  if (timer_create(CLOCK_REALTIME, &sev, &m_timer) == -1)
    SError::throwRuntimeExceptionWithErrno("Unable to initialize timer");
}

void SEventThread::Timer::destroy() {
  if (m_timer != NULL) {
    stop();
    timer_delete(m_timer);
    m_timer = NULL;
  }
}

void SEventThread::Timer::start() {
  // std::cout << "SEventThread::Timer::start() 1" << std::endl;
  if (m_timer == NULL)
    SError::throwRuntimeException("Timer is not initialized");

  struct itimerspec its;
  its.it_value.tv_sec     = m_interval / 1000;              // seconds
  its.it_value.tv_nsec    = (m_interval % 1000) * 1000000;  // nano-seconds
  its.it_interval.tv_sec  = m_oneshot ? 0 : its.it_value.tv_sec;
  its.it_interval.tv_nsec = m_oneshot ? 0 : its.it_value.tv_nsec;
  // std::cout << "SEventThread::Timer::start() 2 - value.tv_sec=" <<
  // its.it_value.tv_sec << " value.tv_nsec=" << its.it_value.tv_nsec << "
  // interval.tv_sec=" << its.it_interval.tv_sec << " interval.tv_nsec=" <<
  // its.it_interval.tv_nsec << std::endl;
  if (timer_settime(m_timer, 0, &its, NULL) == -1)
    SError::throwRuntimeExceptionWithErrno("Unable to start timer");
  // std::cout << "SEventThread::Timer::start() 3" << std::endl;
}

void SEventThread::Timer::stop() {
  if (m_timer != NULL) {
    struct itimerspec its;
    its.it_value.tv_sec     = 0;  // seconds
    its.it_value.tv_nsec    = 0;  // nano-seconds
    its.it_interval.tv_sec  = its.it_value.tv_sec;
    its.it_interval.tv_nsec = its.it_value.tv_nsec;
    timer_settime(m_timer, 0, &its, NULL);
  }
}

void SEventThread::Timer::_timerHandler(
    int signo, siginfo_t* pinfo, void* pcontext) {
  // std::cout << "SEventThread::Timer::_timerHandler() 1" << std::endl;
  SEventThread::Timer* pTimer =
      (SEventThread::Timer*) pinfo->si_value.sival_ptr;
  if (pTimer) {
    // std::cout << "SEventThread::Timer::_timerHandler() 2" << std::endl;
    pTimer->m_thread->postMessage(new STimerMessage(pTimer));
  }
}

SEventThread::TimerHandler _initTimerHandler;

void SEventThread::TimerHandler::init() {
  // std::cout << "SEventThread::TimerHandler::init()" << std::endl;
  struct sigaction sa;
  sa.sa_flags     = SA_SIGINFO;
  sa.sa_sigaction = SEventThread::Timer::_timerHandler;
  sigemptyset(&sa.sa_mask);
  int signo = SIGRTMIN;
  if (sigaction(signo, &sa, NULL) == -1)
    SError::throwRuntimeExceptionWithErrno("Unable to register timer handler");
}

void SEventThread::TimerHandler::uninit() {
  // std::cout << "SEventThread::TimerHandler::uninit()" << std::endl;
}
